package mux

import (
	"path"
	"strings"
	"sync"

	http "github.com/valyala/fasthttp"

	mux "xp/lib/mux/router"
	"xp/lib/utils"
)

type Option func(*Router)

type Handler func(*Ctx)

type Middleware func(Handler) Handler

type Router struct {
	sync.Mutex
	*mux.Router
	Proto

	pathPrefix  string
	middlewares []Middleware
	trace       bool
}

func New(pathPrefix string, opts ...Option) *Router {
	router := &Router{
		Router:      mux.New(),
		pathPrefix:  pathPrefix,
		middlewares: make([]Middleware, 0),
		trace:       true,
	}
	router.HandleMethodNotAllowed = false
	router.NotFound = DefaultNotFoundHandler
	for _, opt := range opts {
		opt(router)
	}
	return router
}

func NewWithProto(pathPrefix string, proto Proto, opts ...Option) *Router {
	router := New(pathPrefix, opts...)
	router.Proto = proto
	return router
}

func (router *Router) handlerWrapper(
	p string, next Handler, opts ...Option) (string, mux.Handle) {

	name := utils.FuncName(next)
	clone := router.clone(router.pathPrefix, false, opts...)
	for i := len(clone.middlewares) - 1; i >= 0; i-- {
		next = clone.middlewares[i](next)
	}
	handler := func(ctx *http.RequestCtx, ps mux.Params) {
		c, err := newCtx(ctx, ps, router.Proto, name, router.trace)
		if err == nil {
			next(c)
		}
		c.flush()
		c.release()
	}
	return path.Join(clone.pathPrefix, p), handler
}

func (router *Router) Clone(pathPrefix string, opts ...Option) *Router {
	return router.clone(pathPrefix, true, opts...)
}

func (router *Router) clone(pathPrefix string, force bool, opts ...Option) *Router {
	// 如果没有对当前的router进行修改，默认使用当前的router
	// 避免没有必要的拷贝
	if !force && len(opts) == 0 {
		return router
	}
	clone := &Router{
		Proto:       router.Proto,
		Router:      router.Router,
		pathPrefix:  pathPrefix,
		middlewares: make([]Middleware, len(router.middlewares)),
		trace:       router.trace,
	}
	copy(clone.middlewares, router.middlewares)
	for _, opt := range opts {
		opt(clone)
	}
	return clone
}

/* 导出方法 */

func (router *Router) Deck(pathPrefix string, opts ...Option) {
	router.clone(pathPrefix, false, opts...)
}

func (router *Router) Use(middlewares ...Middleware) {
	router.middlewares = append(router.middlewares, middlewares...)
}

func (router *Router) UseOnly(middlewares ...Middleware) {
	router.middlewares = append([]Middleware{}, middlewares...)
}

func DisableTrace(r *Router) {
	r.trace = false
}

/* 支持的HTTP method */

func (router *Router) Get(path string, handler Handler, opts ...Option) {
	router.GET(router.handlerWrapper(path, handler, opts...))
}

func (router *Router) Post(path string, handler Handler, opts ...Option) {
	router.POST(router.handlerWrapper(path, handler, opts...))
}

func (router *Router) Put(path string, handler Handler, opts ...Option) {
	router.PUT(router.handlerWrapper(path, handler, opts...))
}

func (router *Router) Delete(path string, handler Handler, opts ...Option) {
	router.DELETE(router.handlerWrapper(path, handler, opts...))
}

func (router *Router) Head(path string, handler Handler, opts ...Option) {
	router.HEAD(router.handlerWrapper(path, handler, opts...))
}

func (router *Router) Options(path string, handler Handler, opts ...Option) {
	router.OPTIONS(router.handlerWrapper(path, handler, opts...))
}

func (router *Router) ServeFiles(p, root string, opts ...Option) {
	if len(p) < 10 || p[len(p)-10:] != "/*filepath" {
		panic("path must end with /*filepath in path '" + p + "'")
	}
	prefix := p[:len(p)-10]

	fs := &http.FS{
		Root:               root,
		IndexNames:         []string{"index.html"},
		GenerateIndexPages: true,
		AcceptByteRange:    true,
	}
	stripSlashes := strings.Count(prefix, "/")
	if stripSlashes > 0 {
		fs.PathRewrite = http.NewPathSlashesStripper(stripSlashes)
	}
	fileHandler := fs.NewRequestHandler()

	next := func(ctx *Ctx) {
		fileHandler(ctx.RequestCtx)
	}
	name := path.Join("ServeFiles", router.pathPrefix, prefix)
	clone := router.clone(router.pathPrefix, false, opts...)
	for i := len(clone.middlewares) - 1; i >= 0; i-- {
		next = clone.middlewares[i](next)
	}
	handler := func(ctx *http.RequestCtx, ps mux.Params) {
		c, err := newCtx(ctx, ps, router.Proto, name, false)
		if err == nil {
			next(c)
		}
		c.flush()
		c.release()
	}
	router.GET(path.Join(clone.pathPrefix, p), handler)
}
